import copy
import json

import numpy as np
from preprocess import GraphFilter
from sklearn.manifold import TSNE


class Processor:
    def __init__(self):
        self.graphFilter = GraphFilter()
        self.perplexity = 5
        with open("./data/graph.json", "r", encoding='utf-8') as file:
            self.rawdata = json.load(file)
            
    def updateFilterCondition(self, condition):
        self.condition = condition
        
    def getFilterData(self, condition):
        '''
            condition format: {nodeType:[], edgeType:[]}
        '''
        data = copy.deepcopy(self.rawdata)
        nodeType = condition.get("nodeType", [])
        edgeType = condition.get("edgeType", [])
        nodes = [node for node in data["nodes"] if node["type"] in nodeType]
        nodeIds = [node["id"] for node in nodes]
        links = [link for link in data["links"] if link["type"] in edgeType and link["source"] in nodeIds and link["target"] in nodeIds]
        
        data = self.graphFilter.filter({"nodes": nodes, "links": links})
        data["status"] = "success"
        return data

    def updateData(self, data):
        self.data = data

    def getEmbedNodeData(self):
        '''
            use tsne to reduce data by neighor-node type
            input format: {nodes: [{id:xxx, type:xxx, group:xxx}], links: [source:xxx, target:xxx, value:xxx]}
            output format: {nodes: [{id:xxx, type:xxx, x:xxx, y:xxx, group:xxx}]}
        '''

        nodes = self.data["nodes"]
        links = self.data["links"]
        nodeMap = {}
        typeList = []
        for node in nodes:
            nodeMap[node["id"]] = node
            if node["type"] not in typeList:
                typeList.append(node["type"])

        if (len(typeList) < 2):
            return {"status": "error", "message": "too few node type"}

        neighborInfo = {}
        for node in nodes:
            neighborInfo[node["id"]] = {}
        for link in links:
            source = link["source"]
            target = link["target"]
            neighborInfo[source][nodeMap[target]["type"]] = neighborInfo[source].get(
                nodeMap[target]["type"], 0) + link["value"]
            neighborInfo[target][nodeMap[source]["type"]] = neighborInfo[target].get(
                nodeMap[source]["type"], 0) + link["value"]

        nodeValue = {}
        for (nodeId, neighbor) in neighborInfo.items():
            value = [neighbor.get(type, 0) for type in typeList]
            s = sum(value) + 1e-6
            value = [v / s for v in value]
            nodeValue[nodeId] = value

        ids = np.array(list(nodeValue.keys()))
        X = np.array(list(nodeValue.values()))
        tsne = TSNE(n_components=2, perplexity=min(self.perplexity, len(nodes)-1))
        X_tsne = tsne.fit_transform(X)
        
        for i in range(len(ids)):
            nodeMap[ids[i]]["x"] = float(X_tsne[i][0])
            nodeMap[ids[i]]["y"] = float(X_tsne[i][1])

        return {"status": "success", "nodes": nodes}

    def getEmbedEdgeData(self):
        '''
            use tsne to reduce data by out-edge type
            input format: {nodes: [{id:xxx, type:xxx, group:xxx}], links: [source:xxx, target:xxx, value:xxx, type:xxx]}
            output format: {nodes: [{id:xxx, type:xxx, x:xxx, y:xxx, group:xxx}]}
        '''

        nodes = self.data["nodes"]
        links = self.data["links"]
        nodeMap = {}
        typeList = []
        for node in nodes:
            nodeMap[node["id"]] = node
        for link in links:
            if link["type"] not in typeList:
                typeList.append(link["type"])

        if (len(typeList) < 2):
            return {"status": "error", "message": "too few edge type"}

        neighborInfo = {}
        for node in nodes:
            neighborInfo[node["id"]] = {}
        for link in links:
            source = link["source"]
            neighborInfo[source][link["type"]] = neighborInfo[source].get(
                link["type"], 0) + link["value"]

        nodeValue = {}
        for (nodeId, neighbor) in neighborInfo.items():
            value = [neighbor.get(type, 0) for type in typeList]
            s = sum(value) + 1e-6
            value = [v / s for v in value]
            nodeValue[nodeId] = value

        ids = np.array(list(nodeValue.keys()))
        X = np.array(list(nodeValue.values()))
        tsne = TSNE(n_components=2, perplexity=min(self.perplexity, len(nodes)-1))
        X_tsne = tsne.fit_transform(X)
        
        for i in range(len(ids)):
            nodeMap[ids[i]]["x"] = float(X_tsne[i][0])
            nodeMap[ids[i]]["y"] = float(X_tsne[i][1])

        return {"status": "success", "nodes": nodes}

    def getCenterRelation(self):
        '''
            use page_rank to calcute relation between center and other nodes
            input format: {center:xxx, nodes: [{id:xxx, type:xxx, group:xxx}], links: [source:xxx, target:xxx, value:xxx]}
            output format: {nodes: [{id:xxx, type:xxx, group:xxx, relation:xxx}]}
        '''

        if (self.data.get("center", 0) == 0):
            return {"status": "error", "message": "center not found"}
        
        nodes = self.data["nodes"]
        links = self.data["links"]
        center = self.data["center"]
        n = len(nodes)
        nodeMap = {}
        for i in range(n):
            node = nodes[i]
            nodeMap[node["id"]] = i
        
        # calculate relation matrix
        relation_matrix = np.zeros((n, n))
        for link in links:
            u = nodeMap[link["source"]]
            v = nodeMap[link["target"]]
            relation_matrix[u][v] += link["value"]
            relation_matrix[v][u] += link["value"]
        relation = self.pageRank(relation_matrix)

        for i in range(n):
            nodes[i]["relation"] = float(relation[0][i])
        
        return {"status": "success", "nodes": nodes}
            
            
    def pageRank(self, relation_matrix, alpha=0.85, max_iter=1000, epsilon=1e-6):
        '''
            use page_rank to calcute relation by relation_matrix
        '''
        sum = np.sum(relation_matrix, axis=1)
        edgeW = relation_matrix / 1
        n = relation_matrix.shape[0]
        # 将每个点的出边边权归一化
        for i in range(n):
            if sum[i] > 1e-12:
                for j in range(n):
                    edgeW[i][j] /= sum[i]
        ws = np.ones((1, n)) / n
        for i in range(max_iter):
            ws_last = ws
            ws = (1 - alpha) + alpha * np.dot(ws, edgeW)
            if np.linalg.norm(ws - ws_last) < epsilon:
                break
        return ws

if __name__ == "__main__":
    processor = Processor()
